/**
 * BGPCrasher.java
 *
 * @author BJ Premore
 */


package SSF.OS.BGP4.Widgets;


import java.util.*;
import com.renesys.raceway.SSF.*;
import com.renesys.raceway.DML.*;
import SSF.Net.*;
import SSF.OS.*;
import SSF.OS.BGP4.*;
import SSF.OS.BGP4.Comm.*;
import SSF.OS.BGP4.Timing.*;
import SSF.OS.BGP4.Path.*;
import SSF.OS.BGP4.Util.*;
import SSF.Util.Random.RandomStream;


// ===== class SSF.OS.BGP4.Widgets.BGPCrasher ============================== //
/**
 * A test class, masquerading as a ProtocolSession, for killing the BGP process
 * at random times during a simulation.
 */
public class BGPCrasher extends ProtocolSession {

  // ........................ member data .......................... //

  /** The BGPSession with which this tester is associated. */
  private BGPSession bgp;

  /** The "phase" of the BGPCrasher's process action() method.  In order to
   *  coerce the process object below into being "simple" (for performance
   *  reasons), we can only have wait statements at the very end of the method.
   *  A value of 1 indicates the setup phase, 2 indicates the alive phase,
   *  3 indicates the dead phase, and 4 indicates the stable phase. */
  private int phase = 1;

  /** The simulation time, in seconds, which elapses before BGP becomes
   *  susceptible to crashing. */
  private double waitTime;

  /** Whether or not a random value between 0 and 'frequency' should be added
   *  to the initial wait time value ('waitTime'). */
  private boolean randomize = true;

  /** The frequency, in seconds, at which the BGP process becomes eligible for
   *  possible crashing. */
  private double frequency;

  /** The probability that BGP will crash during any one crash interval.  The
   *  value must be in the range [0.0,1.0]. */
  private double crashProb;

  /** The time, in seconds, that BGP remains dead after crashing.  (After this
   *  amount of time elapses following a crash, BGP is restarted. */
  private double deadTime;

  /** The simulation time, in seconds, beyond which BGP will no longer
   *  crash.  By default, BGP never becomes stable (-1). */
  private double stableTime = -1;

  /** A random stream. */
  private RandomStream rng;

  /** A reference to this BGPCrasher. */
  private BGPCrasher crasher;

  // ----- BGPCrasher() ---------------------------------------------------- //
  /**
   * We must have a zero-argument, public constructor (so that
   * <code>newInstance()</code> can be used to create a new
   * <code>BGPCrasher</code> instance).
   */
  public BGPCrasher() {
    crasher = this;
  }

  // ----- config ---------------------------------------------------------- //
  /**
   * Set the configurable values (specified in DML file).
   *
   * @exception configException  if any of the calls to <code>find</code>
   *                             or <code>findSingle</code> throw such an
   *                             exception.
   */
  public void config(Configuration cfg) throws configException {
    super.config(cfg);

    String str;
    str = (String)cfg.findSingle("waitTime");
    if (str == null) {
      System.out.println("BGPCrasher missing 'waitTime' attribute");
    } else {
      waitTime = Double.valueOf(str).doubleValue();
    }

    str = (String)cfg.findSingle("randomize");
    if (str != null) {
      randomize = Boolean.valueOf(str).booleanValue();
    }

    str = (String)cfg.findSingle("frequency");
    if (str == null) {
      System.out.println("BGPCrasher missing 'frequency' attribute");
    } else {
      frequency = Double.valueOf(str).doubleValue();
    }

    str = (String)cfg.findSingle("crashProb");
    if (str == null) {
      System.out.println("BGPCrasher missing 'crashProb' attribute");
    } else {
      crashProb = Double.valueOf(str).doubleValue();
    }

    str = (String)cfg.findSingle("deadTime");
    if (str == null) {
      System.out.println("BGPCrasher missing 'deadTime' attribute");
    } else {
      deadTime = Double.valueOf(str).doubleValue();
    }

    str = (String)cfg.findSingle("stableTime");
    if (str != null) {
      stableTime = Double.valueOf(str).doubleValue();
    }

  }

  // ----- init ------------------------------------------------------------ //
  /**
   * Creates an SSF process which first waits a while, then kills BGP once in a
   * while, restarting it again after each crash.
   */
  public void init() {
    new process(inGraph()) {
        public boolean isSimple() {
          return true;
        }

        public void action() {
          double timeToWait = -1.0;
          switch (phase) {
          case 1: // setup phase
            try {
              bgp = (BGPSession)inGraph().SessionForName("bgp");
              if (randomize) {
                rng = Net.accessRandomStream(crasher, "BGPCrasher@"+bgp.nh);
                waitTime += rng.nextDouble()*frequency;
              }
            } catch (ProtocolException pex) {
              throw new Error("BGPCrasher couldn't get handle to BGP");
            }
            timeToWait = waitTime;
            phase = 2;
            break;
          case 2: // alive phase
            if (stableTime != -1 && now() >= stableTime) {
              timeToWait = 999999999;  // infinitely, essentially
              phase = 4;
            } else {
              if (bgp.rng2.nextDouble() < crashProb) {
                bgp.die();
                timeToWait = deadTime;
                phase = 3;
              } else { // remain in phase 2 (alive)
                timeToWait = frequency;
              }
            }
            break;
          case 3:
            bgp.restart();
            timeToWait = frequency;
            phase = 2;
            break;
          case 4:
            timeToWait = 999999999; // infinity wasn't long enough--wait more
            break;
          }
          waitFor(Net.seconds(timeToWait));
        }
      };
  }

  // ----- now ------------------------------------------------------------- //
  /**
   * Returns the current simulation time.  A convenience method so that
   * functions, not just Processes, can get the current simulation time.
   *
   * @return the current simulation time
   */
  public double now() {
    return ((double)(inGraph().now()))/((double)Net.frequency);
  }

  // ----- push ------------------------------------------------------------ //
  /**
   * Handles incoming events.
   */
  public boolean push(ProtocolMessage message, ProtocolSession fromSession) {
    // there will never be any incoming events in this pseudo-ProtocolSession
    throw new Error("BGPCrasher.push() called");
  }

} // end class BGPCrasher
